#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<unordered_map>
#include<fstream>
#include<mutex>
#include"util.hpp"

namespace ns_index
{
  struct DocInfo
  {
    std::string title;
    std::string content;
    std::string url;
    uint64_t doc_id;
  };

  struct InvertedElem//倒排节点
  {
    uint64_t doc_id;
    std::string word;
    int weight;

    InvertedElem():weight(0){}
  };

  typedef std::vector<InvertedElem> InvertedList;
  class Index
  {
  private:
    std::vector<DocInfo> forward_index;//倒排拉链

    std::unordered_map<std::string,InvertedList> inverted_index;
  private://单例化index
    Index(){}
    Index(const Index&)=delete;
    Index& operator=(const Index&)=delete;

    static Index* instance;
    static std::mutex mtx;
  public:
    static Index* GetInstance()
    {
      if(nullptr==instance)
      {
        mtx.lock();
        instance=new Index();
        mtx.unlock();
      }
      return instance;
    }
    ~Index()
    {}

  public:
    DocInfo* GetForwardIndex(uint64_t doc_id)//根据文件id获取文件内容
    {
      if(doc_id>=forward_index.size())
      {
        std::cerr<<"doc_id out range,error!"<<std::endl;
        return nullptr;
      }

      return &(forward_index[doc_id]);
    }

    InvertedList* GetInvertedList(const std::string &word)//根据关键词获得倒排拉链
    {
      auto iter=inverted_index.find(word);
      if(iter==inverted_index.end())
      {
        std::cerr<<word<<" have no InvertedList!"<<std::endl;
        return nullptr;
      }
      return &(iter->second);
    }

    bool BuildIndex(const std::string &input)//获取正排和倒排相关数据
    {
      std::fstream in(input,std::ios::in|std::ios::binary);
      if(!in.is_open())
      {
        std::cerr<<"sorry "<<input<<" open error"<<std::endl;
        return false; 
      }
      std::string line;
      int count=0;
      while(std::getline(in,line))
      {
        DocInfo* doc=BuildForwardIndex(line);
        if(doc==nullptr)
        {
          std::cerr<<"build "<<line<<" error"<<std::endl;
          continue;
        }
        BuildInvertedIndex(*doc);
        count++;
        if(count%50==0)
        {
          //std::cout<<"当前已经处理了"<<count<<"个索引"<<std::endl;
          LOG(NORMAL,"当前已建立"+std::to_string(count)+"个索引节点");
        }
      }

      return true;
    }
  private:
    DocInfo* BuildForwardIndex(const std::string line)//构造正排表
    {
      std::vector<std::string> results;
      const std::string sep="\3";
      ns_util::StringUtil::Split(line,&results,sep);//对输入字符串进行切分
      if(results.size()!=3)
      {
        return nullptr;
      }

      DocInfo doc;//填充文件
      doc.title=results[0];
      doc.content=results[1];
      doc.url=results[2];
      doc.doc_id=forward_index.size();

      //插入正排索引
      forward_index.push_back(std::move(doc));
      return &(forward_index.back());
    }

    bool BuildInvertedIndex(const DocInfo &doc)//构造倒排表
    {
      struct word_cnt//词频
      {
        int title_cnt;
        int connect_cnt;

        word_cnt():title_cnt(0),connect_cnt(0){}
      };
      
      std::unordered_map<std::string,word_cnt> word_map;//每个词在文件中出现次数的映射

      std::vector<std::string> title_words;//对标题进行分词
      ns_util::JiebaUtil::CutString(doc.title,&title_words);
      for(auto &s:title_words)//统计词频
      {
        boost::to_lower(s);
        word_map[s].title_cnt++;
      }

      std::vector<std::string> connect_words;//对内容进行分词
      ns_util::JiebaUtil::CutString(doc.content,&connect_words);
      for(auto &s:connect_words)
      {
        boost::to_lower(s);
        word_map[s].connect_cnt++;
      }

#define X 10
#define Y 1
      for(auto &word_pair:word_map)
      {
        InvertedElem item;//填充倒排索引节点
        item.doc_id=doc.doc_id;
        item.word=word_pair.first;
        item.weight=X*word_pair.second.title_cnt+Y*word_pair.second.connect_cnt;
        InvertedList& invertedList=inverted_index[word_pair.first];//将该词插入倒排索引
        invertedList.push_back(std::move(item));
      }
      return true;
    }
  };
  Index* Index::instance=nullptr;
  std::mutex Index::mtx;
}
